package business.services;

import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Vector;

import common.Contexto;
import business.interfaces.ICalendarioReserva;
import business.interfaces.ICancha;
import business.interfaces.ICliente;
import business.interfaces.IReservaCancha;
import data.entities.CalendarioReserva;
import data.entities.Cancha;
import data.entities.Cliente;
import data.entities.ReservaCancha;
import data.repository.IRepository;
import data.repository.QueryParameter;
import data.repository.Repository;
import data.repository.RepositoryException;

public class ServicioReservas implements IServicioReservas {
	private IRepository<ReservaCancha> repositorioReservas;
	private IRepository<CalendarioReserva> repositorioCalendarios;
	private IServicioClientes servicioClientes;
	private IServicioCanchas servicioCanchas;
	
	public ServicioReservas(IServicioClientes servicioClientes, IServicioCanchas servicioCanchas) {
		this.repositorioReservas = new Repository<ReservaCancha>();
		this.repositorioCalendarios = new Repository<CalendarioReserva>();
		this.servicioClientes = servicioClientes;
		this.servicioCanchas = servicioCanchas;
	}
	
	public void generarCalendarioReservasMensual(Boolean sobreescribirCalendarioExistente) throws ServiceException {
		try {	
			List<CalendarioReserva> calendarioReservas = this.repositorioCalendarios.getAll(CalendarioReserva.class);
			
			if(!calendarioReservas.isEmpty() && !sobreescribirCalendarioExistente) {
				throw new ServiceException("No se puede sobreescribir el calendario actual de reservas");
			}
			
			List<ICancha> canchas = this.servicioCanchas.getCanchasParaReservas();
			Calendar calendar = Calendar.getInstance();
	
			calendar.set(Contexto.getAnioActual(), Contexto.getMesActual() - 1, 1);
			
			Date fecha = calendar.getTime();
			Integer diasMesActual = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
			
			for(Integer dia = 1; dia <= diasMesActual; dia++) {
				if(dia > 1) {
					calendar.add(Calendar.DATE, 1);
					fecha = calendar.getTime();
				}
				
				for(Integer hora = 1; hora <= 24; hora ++) {	
					for(ICancha cancha : canchas) {
						CalendarioReserva calendarioReserva = new CalendarioReserva();
						
						calendarioReserva.setFecha(fecha);
						calendarioReserva.setHora(hora);
						calendarioReserva.setCancha((Cancha) cancha);
						calendarioReserva.setEstaLibre(true);
						
						this.repositorioCalendarios.saveOrUpdate(calendarioReserva);
					}
				}
			}	
		} catch (RepositoryException e) {
			throw new ServiceException(e);
		}
	}
	
	public List<IReservaCancha> getReservas() throws ServiceException {
		try {
			List<ReservaCancha> reservas = this.repositorioReservas.getAll(ReservaCancha.class);
			
			return this.getReservas(reservas);
		} catch (RepositoryException e) {
			throw new ServiceException(e);
		}
	}

	public List<IReservaCancha> getReservas(Date fecha) throws ServiceException {		
		try {
			String queryExpression = "from ReservaCancha r where day(r.fecha) = day(:fecha) and month(r.fecha) = month(:fecha) and year(r.fecha) = year(:fecha)";
			QueryParameter parameter = new QueryParameter("fecha", fecha);
			List<ReservaCancha> reservas = this.repositorioReservas.getAll(queryExpression, parameter);
			
			return this.getReservas(reservas);
		} catch (RepositoryException e) {
			throw new ServiceException(e);
		}
	}
	
	public List<IReservaCancha> getReservas(Date fecha, Integer hora) throws ServiceException {		
		try {
			String queryExpression = "from ReservaCancha r where day(r.fecha) = day(:fecha) and month(r.fecha) = month(:fecha) and year(r.fecha) = year(:fecha) and r.hora = :hora";
			QueryParameter parameter1 = new QueryParameter("fecha", fecha);
			QueryParameter parameter2 = new QueryParameter("hora", hora);
			List<ReservaCancha> reservas = this.repositorioReservas.getAll(queryExpression, parameter1, parameter2);
			
			return this.getReservas(reservas);
		} catch (RepositoryException e) {
			throw new ServiceException(e);
		}
	}
	
	public List<IReservaCancha> getReservas(Date fechaInicio, Date fechaFin) throws ServiceException {
		try {
			String queryExpression = "from ReservaCancha r where day(r.fecha) >= day(:fechaInicio) and month(r.fecha) >= month(:fechaInicio) and year(r.fecha) >= year(:fechaInicio) and day(r.fecha) <= day(:fechaFin) and month(r.fecha) <= month(:fechaFin) and year(r.fecha) <= year(:fechaFin)";
			QueryParameter parameter1 = new QueryParameter("fechaInicio", fechaInicio);
			QueryParameter parameter2 = new QueryParameter("fechaFin", fechaFin);
			List<ReservaCancha> reservas = this.repositorioReservas.getAll(queryExpression, parameter1, parameter2);
			
			return this.getReservas(reservas);
		} catch (RepositoryException e) {
			throw new ServiceException(e);
		}
	}

	public IReservaCancha getReserva(String dni) throws ServiceException {
		try {
			return this.getReservaExistente(dni);
		} catch (RepositoryException e) {
			throw new ServiceException(e);
		}
	}
	
	public Boolean hayCanchasDisponibles(Date fecha, Integer hora) throws ServiceException {
		String queryExpression = "from CalendarioReserva c where day(c.fecha) = day(:fecha) and month(c.fecha) = month(:fecha) and year(c.fecha) = year(:fecha) and c.hora = :hora and c.estaLibre = :estaLibre";
		QueryParameter parameter1 = new QueryParameter("fecha", fecha);
		QueryParameter parameter2 = new QueryParameter("hora", hora);
		QueryParameter parameter3 = new QueryParameter("estaLibre", true);
		List<CalendarioReserva> calendarioReservas = new Vector<CalendarioReserva>();
		
		try {
			calendarioReservas = this.repositorioCalendarios.getAll(queryExpression, parameter1, parameter2, parameter3);
		} catch (RepositoryException e) {
			throw new ServiceException(e);
		}
		
		Boolean hayDisponibles = !calendarioReservas.isEmpty();
		
		return hayDisponibles;
	}

	public Boolean hayCanchasDisponibles(Date fecha) throws ServiceException {
		String queryExpression = "from CalendarioReserva c where day(c.fecha) = day(:fecha) and month(c.fecha) = month(:fecha) and year(c.fecha) = year(:fecha) and c.estaLibre = :estaLibre";
		QueryParameter parameter1 = new QueryParameter("fecha", fecha);
		QueryParameter parameter2 = new QueryParameter("estaLibre", true);
		List<CalendarioReserva> calendarioReservas = new Vector<CalendarioReserva>();
		
		try {
			calendarioReservas = this.repositorioCalendarios.getAll(queryExpression, parameter1, parameter2);
		} catch (RepositoryException e) {
			throw new ServiceException(e);
		}
		
		Boolean hayDisponibles = !calendarioReservas.isEmpty();
		
		return hayDisponibles;
	}
	
	public Boolean esCanchaDisponible(Integer numeroCancha, Date fecha, Integer hora) throws ServiceException {
		CalendarioReserva calendarioReserva;
		
		try {
			calendarioReserva = this.getCalendarioReserva(numeroCancha, fecha, hora);
		} catch (RepositoryException e) {
			throw new ServiceException(e);
		}
		
		return calendarioReserva != null && calendarioReserva.getEstaLibre();
	}
	
	public List<ICalendarioReserva> getCanchasDisponibles(Date fecha, Integer hora) throws ServiceException {
		String queryExpression = "from CalendarioReserva c where day(c.fecha) = day(:fecha) and month(c.fecha) = month(:fecha) and year(c.fecha) = year(:fecha) and c.hora = :hora and c.estaLibre = :estaLibre";
		QueryParameter parameter1 = new QueryParameter("fecha", fecha);
		QueryParameter parameter2 = new QueryParameter("hora", hora);
		QueryParameter parameter3 = new QueryParameter("estaLibre", true);
		List<CalendarioReserva> calendarioReservas = new Vector<CalendarioReserva>();
		
		try {
			calendarioReservas = this.repositorioCalendarios.getAll(queryExpression, parameter1, parameter2, parameter3);
		} catch (RepositoryException e) {
			throw new ServiceException(e);
		}
		
		List<ICalendarioReserva> canchasDisponibles = new Vector<ICalendarioReserva>();
		
		for(CalendarioReserva calendarioReserva : calendarioReservas) {
			canchasDisponibles.add(calendarioReserva);
		}
		
		return canchasDisponibles;
	}
	
	public List<ICalendarioReserva> getCanchasDisponibles(Date fecha) throws ServiceException {
		String queryExpression = "from CalendarioReserva c where day(c.fecha) = day(:fecha) and month(c.fecha) = month(:fecha) and year(c.fecha) = year(:fecha) and c.estaLibre = :estaLibre";
		QueryParameter parameter1 = new QueryParameter("fecha", fecha);
		QueryParameter parameter2 = new QueryParameter("estaLibre", true);
		List<CalendarioReserva> calendarioReservas = new Vector<CalendarioReserva>();
		
		try {
			calendarioReservas = this.repositorioCalendarios.getAll(queryExpression, parameter1, parameter2);
		} catch (RepositoryException e) {
			throw new ServiceException(e);
		}
		
		List<ICalendarioReserva> canchasDisponibles = new Vector<ICalendarioReserva>();
		
		for(CalendarioReserva calendarioReserva : calendarioReservas) {
			canchasDisponibles.add(calendarioReserva);
		}
		
		return canchasDisponibles;
	}
	
	public ICalendarioReserva buscarMejorCanchaDisponible(Date fecha, Integer hora) throws ServiceException {
		ICalendarioReserva mejorCanchaDisponible = null;
		List<ICalendarioReserva> canchasDisponibles = this.getCanchasDisponibles(fecha);
		
		if(!canchasDisponibles.isEmpty()) {
			mejorCanchaDisponible = this.buscarCanchaDisponiblePorHora(canchasDisponibles, hora);
		} else {
			if (Contexto.getDia(fecha) < 28) {
				Calendar calendar = Calendar.getInstance();

				calendar.setTime(fecha); 
				calendar.add(Calendar.DATE, 1);
				
				Date fechaPosterior = calendar.getTime();
				
				mejorCanchaDisponible = this.buscarMejorCanchaDisponible(fechaPosterior, hora);
			}
		}
		
		return mejorCanchaDisponible;
	}
	
	public void reservarCancha(String dni, Date fecha, Integer hora, Double senia) throws ServiceException {
		List<ICalendarioReserva> canchasDisponibles = this.getCanchasDisponibles(fecha, hora);
		
		if(canchasDisponibles.isEmpty()) {
			throw new ServiceException("No hay canchas disponibles para reservar");
		}

		CalendarioReserva calendarioReserva = (CalendarioReserva) canchasDisponibles.get(0);
		
		this.reservarCancha(dni, calendarioReserva, senia);
	}

	public void reservarCancha(String dni, Date fecha, Integer hora, Integer numeroCancha,
			Double senia) throws ServiceException {
		CalendarioReserva calendarioReserva;
		
		try {
			calendarioReserva = this.getCalendarioReserva(numeroCancha, fecha, hora);
		} catch (RepositoryException e) {
			throw new ServiceException(e);
		}
		
		if(calendarioReserva == null || !calendarioReserva.getEstaLibre()) {
			String mensaje = String.format("La cancha %s no esta disponible para reservar", numeroCancha);
			
			throw new ServiceException(mensaje);
		}
		
		this.reservarCancha(dni, calendarioReserva, senia);
	}
	
	public void cancelarReserva(String dni) throws ServiceException {
		try {
			ReservaCancha reservaExistente = this.getReservaExistente(dni);
			
			if(reservaExistente == null || !reservaExistente.getEstaActiva()) {
				String mensaje = String.format("No existe reserva activa para %s", dni);
				
				throw new ServiceException(mensaje);
			}
			
			reservaExistente.setEstaActiva(false);
			this.repositorioReservas.saveOrUpdate(reservaExistente);
			
			CalendarioReserva calendarioReserva = (CalendarioReserva) reservaExistente.getCalendario();
			
			calendarioReserva.setEstaLibre(true);
			
			this.repositorioCalendarios.saveOrUpdate(calendarioReserva);
		} catch (RepositoryException e) {
			throw new ServiceException(e);
		}
	}

	private List<IReservaCancha> getReservas(List<ReservaCancha> reservas) {
		List<IReservaCancha> result = new Vector<IReservaCancha>();
		
		for(ReservaCancha reserva : reservas) {
			result.add(reserva);
		}
		
		return result;
	}
	
	private ReservaCancha getReservaExistente(String dni) throws RepositoryException {
		String queryExpression = "from ReservaCancha r where r.cliente.dni = :dni and r.estaActiva = :estaActiva";
		QueryParameter parameter1 = new QueryParameter("dni", dni);
		QueryParameter parameter2 = new QueryParameter("estaActiva", true);
		ReservaCancha reserva = this.repositorioReservas.get(queryExpression, parameter1, parameter2);
		
		return reserva;	
	}
	
	private CalendarioReserva getCalendarioReserva(Integer numeroCancha, Date fecha, Integer hora) throws RepositoryException {
		String queryExpression = "from CalendarioReserva c where day(c.fecha) = day(:fecha) and month(c.fecha) = month(:fecha) and year(c.fecha) = year(:fecha) and c.hora = :hora and c.cancha.numero = :numeroCancha";
		QueryParameter parameter1 = new QueryParameter("fecha", fecha);
		QueryParameter parameter2 = new QueryParameter("hora", hora);
		QueryParameter parameter3 = new QueryParameter("numeroCancha", numeroCancha);
		CalendarioReserva calendarioReserva = this.repositorioCalendarios.get(queryExpression, parameter1, parameter2, parameter3);
		
		return calendarioReserva;
	}
	
	private ICalendarioReserva buscarCanchaDisponiblePorHora(List<ICalendarioReserva> canchasDisponibles, Integer hora) {
		ICalendarioReserva canchaDisponibleEncontrada = null;
		
		for(ICalendarioReserva canchaDisponible : canchasDisponibles) {
			if(canchaDisponible.getHora() == hora) {
				canchaDisponibleEncontrada = canchaDisponible;
			}
		}

		if(canchaDisponibleEncontrada == null && hora < 24) {
			Integer newHora = hora + 1;
			
			canchaDisponibleEncontrada = this.buscarCanchaDisponiblePorHora(canchasDisponibles, newHora);
		}
		
		return canchaDisponibleEncontrada;
	}
	
	private void reservarCancha(String dni, CalendarioReserva calendarioReserva, Double senia) throws ServiceException {
		if(senia <= 0 && !this.servicioClientes.esSocio(dni)) {
			String mensaje = String.format("El cliente %s debe abonar seña ya que no es socio", dni);
			
			throw new ServiceException(mensaje);
		}
		
		ICliente cliente = this.servicioClientes.getCliente(dni);
		
		if(cliente == null) {
			this.servicioClientes.registrarCliente(dni, "");
			
			cliente = this.servicioClientes.getCliente(dni);
		}
		
		ReservaCancha newReserva = new ReservaCancha();
		
		newReserva.setCalendario(calendarioReserva);
		newReserva.setCliente((Cliente) cliente);
		newReserva.setEstaActiva(true);
		
		if(senia > 0) {
			newReserva.setSenia(senia);
		}
		
		try {
			this.repositorioReservas.saveOrUpdate(newReserva);
			
			calendarioReserva.setEstaLibre(false);
			
			this.repositorioCalendarios.saveOrUpdate(calendarioReserva);
		} catch (RepositoryException e) {
			throw new ServiceException(e);
		}	
	}
}
